package com.bifang.common.util;

import com.bifang.common.exception.AppException;
import com.bifang.common.vo.ModifiedFieldVO;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

/**
 * 相同实体类字段比较工具类
 *
 * @author chenzh
 */
public class FieldChangeUtil {
    private static final ObjectMapper mapper = new ObjectMapper();

    /**
     * Compare two object and return modified fields
     *
     * @param source source object
     * @param target target object
     * @return the modified fields and value after modify
     */
    public static List<ModifiedFieldVO> getModifyContent(Object source, Object target) {
        List<ModifiedFieldVO> list = new ArrayList<>();
        /*
         process null problem, if all null means equal
         if only source is null means all modified
         if only target is null means nothing changed
        */
        if (null == source || null == target) {
            return list;
        }
        // source and target must be same class type
        if (!Objects.equals(source.getClass().getName(), target.getClass().getName())) {
            throw new AppException("不是相同的类");
        }

        BeanWrapper beanWrapper = new BeanWrapperImpl(source);
        Class sourceCla = (Class) source.getClass();
        Field[] sourceFields = sourceCla.getDeclaredFields();
        for (Field sourceField : sourceFields) {
            if (beanWrapper.isWritableProperty(sourceField.getName())) {
                Object obj = beanWrapper.getPropertyValue(sourceField.getName());
                if (obj != null && obj instanceof Date) {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    try {
                        String dateStr = sdf.format(obj);
                        beanWrapper.setPropertyValue(sourceField.getName(), sdf.parse(dateStr));
                    } catch (Exception e) {

                    }
                }
            }
        }
        source = beanWrapper.getWrappedInstance();

        BeanWrapper beanWrapper2 = new BeanWrapperImpl(target);
        Class targetCla = (Class) target.getClass();
        Field[] targetFields = targetCla.getDeclaredFields();
        for (Field sourceField : targetFields) {
            if (beanWrapper2.isWritableProperty(sourceField.getName())) {
                Object obj = beanWrapper2.getPropertyValue(sourceField.getName());
                if (obj != null && obj instanceof Date) {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    try {
                        String dateStr = sdf.format(obj);
                        beanWrapper2.setPropertyValue(sourceField.getName(), sdf.parse(dateStr));
                    } catch (Exception e) {

                    }
                }
            }
        }
        target = beanWrapper2.getWrappedInstance();

        Map<String, Object> sourceMap =
                mapper.convertValue(source, new TypeReference<Map<String, Object>>() {});
        Map<String, Object> targetMap =
                mapper.convertValue(target, new TypeReference<Map<String, Object>>() {});
        sourceMap.forEach(
                (k, v) -> {
                    Object targetValue = targetMap.get(k);

                    setFieldVOList(k, v, targetValue, list);
                });
        return list;
    }

    /**
     * Compare two object and return modified fields which contain in comparedProperties
     *
     * @param source ource object
     * @param target target object
     * @param comparedProperties the fields need to be compare
     * @return the modified fields and value after modify
     */
    public static List<ModifiedFieldVO> getModifyContent(
            Object source, Object target, Map<String, String> comparedProperties) {
        List<ModifiedFieldVO> list = new ArrayList<>();
        if (null == source || null == target) {
            return list;
        }
        if (!Objects.equals(source.getClass().getName(), target.getClass().getName())) {
            throw new AppException("不是相同的类");
        }

        BeanWrapper beanWrapper = new BeanWrapperImpl(source);
        Class sourceCla = (Class) source.getClass();
        Field[] sourceFields = sourceCla.getDeclaredFields();
        for (Field sourceField : sourceFields) {
            if (beanWrapper.isWritableProperty(sourceField.getName())) {
                Object obj = beanWrapper.getPropertyValue(sourceField.getName());
                if (obj != null && obj instanceof Date) {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    try {
                        String dateStr = sdf.format(obj);
                        beanWrapper.setPropertyValue(sourceField.getName(), sdf.parse(dateStr));
                    } catch (Exception e) {

                    }
                }
            }
        }
        source = beanWrapper.getWrappedInstance();

        BeanWrapper beanWrapper2 = new BeanWrapperImpl(target);
        Class targetCla = (Class) target.getClass();
        Field[] targetFields = targetCla.getDeclaredFields();
        for (Field sourceField : targetFields) {
            if (beanWrapper2.isWritableProperty(sourceField.getName())) {
                Object obj = beanWrapper2.getPropertyValue(sourceField.getName());
                if (obj != null && obj instanceof Date) {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    try {
                        String dateStr = sdf.format(obj);
                        beanWrapper2.setPropertyValue(sourceField.getName(), sdf.parse(dateStr));
                    } catch (Exception e) {

                    }
                }
            }
        }
        target = beanWrapper2.getWrappedInstance();

        Map<String, Object> sourceMap =
                mapper.convertValue(source, new TypeReference<Map<String, Object>>() {});
        Map<String, Object> targetMap =
                mapper.convertValue(target, new TypeReference<Map<String, Object>>() {});
        sourceMap.forEach(
                (k, v) -> {
                    if (comparedProperties != null && !comparedProperties.containsKey(k)) {
                        return;
                    }
                    Object targetValue = targetMap.get(k);
                    setFieldVOList(k, v, targetValue, list);
                });
        return list;
    }

    /***
     * 剔除字段比对两个实体的不同
     * */
    public static List<ModifiedFieldVO> getModifyContentRemove(
            Object source, Object target, Map<String, String> removeProperties) {
        List<ModifiedFieldVO> list = new ArrayList<>();
        if (null == source || null == target) {
            return list;
        }
        if (!Objects.equals(source.getClass().getName(), target.getClass().getName())) {
            throw new AppException("不是相同的类");
        }
        BeanWrapper beanWrapper = new BeanWrapperImpl(source);
        Class sourceCla = (Class) source.getClass();
        Field[] sourceFields = sourceCla.getDeclaredFields();
        for (Field sourceField : sourceFields) {
            if (beanWrapper.isWritableProperty(sourceField.getName())) {
                Object obj = beanWrapper.getPropertyValue(sourceField.getName());
                if (obj != null && obj instanceof Date) {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    try {
                        String dateStr = sdf.format(obj);
                        beanWrapper.setPropertyValue(sourceField.getName(), sdf.parse(dateStr));
                    } catch (Exception e) {

                    }
                }
            }
        }
        source = beanWrapper.getWrappedInstance();

        BeanWrapper beanWrapper2 = new BeanWrapperImpl(target);
        Class targetCla = (Class) target.getClass();
        Field[] targetFields = targetCla.getDeclaredFields();
        for (Field sourceField : targetFields) {
            if (beanWrapper2.isWritableProperty(sourceField.getName())) {
                Object obj = beanWrapper2.getPropertyValue(sourceField.getName());
                if (obj != null && obj instanceof Date) {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    try {
                        String dateStr = sdf.format(obj);
                        beanWrapper2.setPropertyValue(sourceField.getName(), sdf.parse(dateStr));
                    } catch (Exception e) {

                    }
                }
            }
        }
        target = beanWrapper2.getWrappedInstance();

        Map<String, Object> sourceMap =
                mapper.convertValue(source, new TypeReference<Map<String, Object>>() {});
        Map<String, Object> targetMap =
                mapper.convertValue(target, new TypeReference<Map<String, Object>>() {});
        sourceMap.forEach(
                (k, v) -> {
                    if (removeProperties != null && removeProperties.containsKey(k)) {
                        return;
                    }
                    Object targetValue = targetMap.get(k);
                    setFieldVOList(k, v, targetValue, list);
                });
        return list;
    }

    public static void setFieldVOList(
            String k, Object v, Object targetValue, List<ModifiedFieldVO> list) {

        if (!Objects.equals(v, targetValue)) {
            if (v == null) {
                if (StringUtils.isBlank(targetValue.toString().trim())) {
                    return;
                }
            }
            if (targetValue == null) {
                if (StringUtils.isBlank(v.toString().trim())) {
                    return;
                }
            }
            if (v != null && targetValue != null) {
                try {
                    if (v instanceof Double || v instanceof Float || v instanceof BigDecimal) {
                        if (new BigDecimal(v.toString())
                                        .compareTo(new BigDecimal(targetValue.toString()))
                                == 0) {
                            return;
                        }
                    }
                } catch (Exception e) {
                    throw new AppException("日志信息和当前表字段(" + k + ")类型不一致");
                }
            }
            ModifiedFieldVO modifiedFieldVO = new ModifiedFieldVO();
            modifiedFieldVO.setField(k);
            if (v != null) {
                modifiedFieldVO.setBeforeValue(String.valueOf(v));
            }
            if (targetValue != null) {
                modifiedFieldVO.setAfterValue(String.valueOf(targetValue));
            }
            list.add(modifiedFieldVO);
        }
    }
}
